package cn.elwy.rcp.ui.table;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ObjectUtils;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.wb.swt.SWTResourceManager;

import cn.elwy.common.util.AssertUtil;
import cn.elwy.common.util.ReflectUtil;
import cn.elwy.common.util.sort.SortUtil;
import cn.elwy.rcp.core.model.ui.InputModel;
import cn.elwy.rcp.ui.table.adapter.ButtonBarAdapter;
import cn.elwy.rcp.ui.table.adapter.TableEditorAdapter;
import cn.elwy.rcp.ui.table.internal.TablePaneRefreshSupport;
import cn.elwy.rcp.ui.table.listener.TablePaneActionGroup;
import cn.elwy.rcp.ui.table.listener.TablePaneButtonListener;
import cn.elwy.rcp.ui.table.listener.TablePaneDoubleClickListener;
import cn.elwy.rcp.ui.table.listener.TablePaneKeyListener;
import cn.elwy.rcp.ui.table.listener.TablePaneMouseListener;
import cn.elwy.rcp.ui.table.listener.TablePanePropertyListener;
import cn.elwy.rcp.ui.table.listener.TablePaneSelectionListener;
import cn.elwy.rcp.ui.table.model.ButtonBarInfo;
import cn.elwy.rcp.ui.table.model.ButtonCompositeInfo;
import cn.elwy.rcp.ui.table.model.ButtonInfo;
import cn.elwy.rcp.ui.table.model.ColumnInfo;
import cn.elwy.rcp.ui.table.model.TableInfo;
import cn.elwy.rcp.ui.table.model.ViewerFilterInfo;
import cn.elwy.rcp.ui.table.provider.TablePaneFilter;
import cn.elwy.rcp.ui.table.provider.TablePaneLabelProvider;
import cn.elwy.rcp.ui.table.setting.listener.ColumnSettingMenuDetectListener;
import cn.elwy.rcp.ui.util.SWTUtil;

/**
 * 表格面板挺像类
 * @author huangsq
 * @version 1.0, 2014-01-05
 * @since 1.0, 2014-01-05
 */
public abstract class TablePane extends Composite {

  protected TableInfo tableInfo;
  protected InputModel inputModel;

  private MenuManager menuManager;
  private Menu contextMenu;
  protected Map<String, Item> treeColumnMap;

  protected IBaseLabelProvider labelProvider;
  protected ITreeContentProvider contentProvider;
  protected TablePanePropertyListener propertyChangedListener;
  protected TablePaneRefreshSupport tablePaneRefreshSupport;
  protected boolean configChanged;
  private TableEditorAdapter tableEditorAdapter;
  private ViewerFilter[] viewerFilters;

  /**
   * @wbp.parser.constructor
   */
  protected TablePane(Composite parent, String tableId) {
    this(parent, SWT.NONE, tableId);
  }

  protected TablePane(Composite parent, int style, String tableId) {
    this(parent, style, TableConfigFactory.getTableInfo(tableId));
  }

  protected TablePane(Composite parent, int style, TableInfo tableInfo) {
    super(parent, style);
    this.tableInfo = tableInfo;

    GridLayout gridLayout = new GridLayout();
    gridLayout.marginHeight = 0;
    gridLayout.marginWidth = 0;
    gridLayout.verticalSpacing = 0;
    gridLayout.horizontalSpacing = 0;
    setLayout(gridLayout);

    GridData gridData = new GridData(GridData.FILL_BOTH);
    gridData.widthHint = 0;
    setLayoutData(gridData);

    List<ButtonCompositeInfo> buttonCompositeInfoList = tableInfo.getButtonCompositeInfoList();
    if (buttonCompositeInfoList == null) {
      createTableViewer();
    } else {
      for (ButtonCompositeInfo bci : buttonCompositeInfoList) {
        if (bci.isTop()) {
          createButtonComposite(bci);
        }
      }
      createTableViewer();
      for (ButtonCompositeInfo bci : buttonCompositeInfoList) {
        if (bci.isBottom()) {
          createButtonComposite(bci);
        }
      }
    }

    initTableViwer();

    createTablePaneRefreshSupport();
  }

  protected void createButtonComposite(ButtonCompositeInfo bci) {
    List<ButtonBarInfo> buttonBarInfoList = bci.getButtonBarInfoList();
    if (buttonBarInfoList == null) {
      return;
    }
    int size = buttonBarInfoList.size();
    Composite composite = new Composite(this, SWT.NONE);
    GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1);
    composite.setLayoutData(gridData);
    GridLayout gridLayout = new GridLayout(size, false);
    gridLayout.marginHeight = 2;
    gridLayout.marginWidth = 2;
    composite.setLayout(gridLayout);
    for (ButtonBarInfo bbi : buttonBarInfoList) {
      ButtonBarAdapter buttonBarAdapter = bbi.getButtonBarAdapter();
      if (buttonBarAdapter == null) {
        ToolBar toolBar = new ToolBar(composite, SWT.FLAT | SWT.WRAP | SWT.RIGHT | bbi.getStyle());
        toolBar.setLayoutData(new GridData(bbi.getHorizontalAlignment(), bbi.getVerticalAlignment(),
            bbi.getGrabExcessHorizontalSpace(), bbi.getGrabExcessVerticalSpace(), 1, 1));
        createButton(toolBar, bbi);
      } else {
        buttonBarAdapter.createButtonBar(composite, bbi);
      }
    }
  }

  protected void createButton(ToolBar toolBar, ButtonBarInfo buttonBarInfo) {
    List<ButtonInfo> buttonInfos = buttonBarInfo.getButtonInfoList();
    for (ButtonInfo buttonInfo : buttonInfos) {
      ToolItem toolItem = new ToolItem(toolBar, buttonInfo.getStyle());
      if (buttonInfo.getName() != null && !"".equals(buttonInfo.getName())) {
        toolItem.setText(buttonInfo.getName());
      }
      String imagePath = buttonInfo.getImagePath();
      if (imagePath != null && !"".equals(imagePath)) {
        toolItem.setImage(SWTResourceManager.getImage(imagePath));
      }
      TablePaneButtonListener listener = buttonInfo.getListener();
      if (listener != null) {
        listener.setTablePane(this);
        toolItem.addSelectionListener(listener);
      }
    }
  }

  protected abstract void createTableViewer();

  private void initTableViwer() {
    if (getTable() instanceof Table) {
      Table table = (Table) getTable();
      table.setLinesVisible(tableInfo.isLineVisible());
      table.setHeaderVisible(tableInfo.isHeaderVisible());
      table.setLayoutDeferred(true);
      table.addListener(SWT.MeasureItem, new Listener() {
        public void handleEvent(Event event) {
          event.height = tableInfo.getLineHeight();
        }
      });
    } else if (getTable() instanceof Tree) {
      Tree tree = (Tree) getTable();
      tree.setLinesVisible(tableInfo.isLineVisible());
      tree.setHeaderVisible(tableInfo.isHeaderVisible());
      tree.setLayoutDeferred(true);
      tree.addListener(SWT.MeasureItem, new Listener() {
        public void handleEvent(Event event) {
          event.height = tableInfo.getLineHeight();
        }
      });
    }

    createColumns();

    labelProvider = createLabelProvider();
    getTableViewer().setLabelProvider(labelProvider);
    contentProvider = createContentProvider();
    getTableViewer().setContentProvider(contentProvider);

    viewerFilters = createViewerFilter();
    if (viewerFilters != null) {
      getTableViewer().setFilters(viewerFilters);
    }
    createEditorTable();

    addKeyListener();
    addMouseListener();
    addDoubleClickListener();
    addSelectionChangedListener();
    addPropertyChangedListener();
    addCheckStateListener();
    addDragListener();
    addDropListener();
    hookContextMenu();

    if (tableInfo.isCanSort()) {
      addColumnSortListener();
    }
    addDisposeListener(new TablePaneDisposeListener());

    if (tableInfo.isCanSettingColumn()) {
      ColumnSettingMenuDetectListener menuDetectListener = new ColumnSettingMenuDetectListener(this);
      getTable().addMenuDetectListener(menuDetectListener);
    }
  }

  protected void createEditorTable() {
    tableEditorAdapter = tableInfo.getTableEditorAdapter();
    if (tableEditorAdapter != null) {
      tableEditorAdapter.setTablePane(this);
      tableEditorAdapter.init();
    }
  }

  protected void resetEditorTable() {
    if (tableEditorAdapter != null) {
      tableEditorAdapter.resetEditorTable();
    }
  }

  @Override
  public int getStyle() {
    int tableInfoStyle = SWTUtil.toSWTConstant(tableInfo.getStyleName());
    int style = SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | tableInfoStyle;
    if (!tableInfo.isTree()) {
      style = SWT.FULL_SELECTION | style;
    }
    if (tableInfo.isMultipleSelection()) {
      return style | SWT.MULTI;
    }
    return style;
  }

  protected abstract void createColumns();

  protected IBaseLabelProvider createLabelProvider() {
    IBaseLabelProvider provider = tableInfo.getLabelProvider();
    if (provider instanceof TablePaneLabelProvider) {
      ((TablePaneLabelProvider) provider).setTableInfo(tableInfo);
    }
    return provider;
  }

  protected ITreeContentProvider createContentProvider() {
    return tableInfo.getContentProvider();
  }

  protected ViewerFilter[] createViewerFilter() {
    List<ViewerFilterInfo> viewerFilterList = tableInfo.getViewerFilterList();
    ViewerFilter[] filters = null;
    if (AssertUtil.isNotEmpty(viewerFilterList)) {
      filters = new ViewerFilter[viewerFilterList.size()];
      for (int i = 0; i < viewerFilterList.size(); i++) {
        ViewerFilter filter = viewerFilterList.get(i).getViewerFilter();
        filters[i] = filter;
        if (filter instanceof TablePaneFilter) {
          ((TablePaneFilter) filter).setTableInfo(tableInfo);
        }
      }
    }
    return filters;
  }

  protected void addKeyListener() {
    TablePaneKeyListener listener = tableInfo.getKeyListener();
    if (listener != null) {
      listener.setTablePane(this);
      getTable().addKeyListener(listener);
    }
  }

  protected void addMouseListener() {
    TablePaneMouseListener listener = tableInfo.getMouseListener();
    if (listener != null) {
      listener.setTablePane(this);
      getTable().addMouseListener(listener);
    }
  }

  protected void addDoubleClickListener() {
    TablePaneDoubleClickListener listener = tableInfo.getDoubleClickListener();
    if (listener != null) {
      listener.setTablePane(this);
      getTableViewer().addDoubleClickListener(listener);
    }
  }

  protected void addSelectionChangedListener() {
    TablePaneSelectionListener listener = tableInfo.getSelectionChangedListener();
    if (listener != null) {
      listener.setTablePane(this);
      getTableViewer().addSelectionChangedListener(listener);
    }
  }

  protected void addPropertyChangedListener() {
    propertyChangedListener = tableInfo.getPropertyChangedListener();
    if (propertyChangedListener != null) {
      propertyChangedListener.setTablePane(this);
      propertyChangedListener.registListener();
    }
  }

  protected abstract void addColumnSortListener();

  protected abstract void addCheckStateListener();

  protected abstract void addDropListener();

  protected abstract void addDragListener();

  protected void createTablePaneRefreshSupport() {
    tablePaneRefreshSupport = new TablePaneRefreshSupport(this, tableInfo);
    tablePaneRefreshSupport.createTimer();
  }

  public InputModel getInputModel() {
    return inputModel;
  }

  public Object getSelectionData() {
    return getSelection().getFirstElement();
  }

  public Object[] getSelectionDatas() {
    return getSelection().toArray();
  }

  @SuppressWarnings("unchecked")
  public <T> List<T> getSelectionDataList() {
    Object[] selectionDatas = getSelectionDatas();
    if (selectionDatas == null) {
      return null;
    }
    List<T> selectionList = new ArrayList<T>();
    for (Object object : selectionDatas) {
      selectionList.add((T) object);
    }
    return selectionList;
  }

  protected IStructuredSelection getSelection() {
    return (IStructuredSelection) getTableViewer().getSelection();
  }

  protected void setSelection(final Object... selectedDatas) {
    Runnable runnable = new Runnable() {

      @Override
      public void run() {
        if (isViewerDisposed()) {
          return;
        }
        List<Object> newSelectionDatas = getNewSelectionDatas(selectedDatas);
        if (!newSelectionDatas.isEmpty()) {
          getTableViewer().setSelection(new StructuredSelection(newSelectionDatas));
        }
      }

      private List<Object> getNewSelectionDatas(final Object... oldSelectionDatas) {
        Object[] elements = contentProvider.getElements(getTableViewer().getInput());
        List<Object> newSelectionDatas = new ArrayList<Object>();
        List<String> columnKeys = tableInfo.getColumnKeys();
        if (!columnKeys.isEmpty() && oldSelectionDatas.length > 0) {
          for (Object oldSelection : oldSelectionDatas) {
            for (Object element : elements) {
              if (propertyValueEquals(oldSelection, element, columnKeys)) {
                newSelectionDatas.add(element);
              }
            }
          }
        }
        if (newSelectionDatas.isEmpty() && elements.length > 0) {
          newSelectionDatas.add(elements[0]);
        }
        return newSelectionDatas;
      }

      private boolean propertyValueEquals(Object o1, Object o2, List<String> propertyNames) {
        if (propertyNames.isEmpty()) {
          return false;
        }
        Class<?> clazz = o1.getClass();
        if (!clazz.equals(o2.getClass())) {
          return false;
        }
        int equalsCount = 0;
        try {
          boolean isTable = tableInfo.isTable();
          for (String propertyName : propertyNames) {
            Object invoke = null;
            Object invoke2 = null;
            if (isTable) {
              invoke = ReflectUtil.getTableColumnValue(o1, propertyName, true);
              invoke2 = ReflectUtil.getTableColumnValue(o2, propertyName, true);
            } else {
              invoke = ReflectUtil.getTreeColumnValue(o1, propertyName, true);
              invoke2 = ReflectUtil.getTreeColumnValue(o2, propertyName, true);
            }
            if (ObjectUtils.equals(invoke, invoke2)) {
              equalsCount++;
            }
          }
        } catch (Exception e) {
        }
        return equalsCount == propertyNames.size();
      }
    };
    getDisplay().syncExec(runnable);
  }

  public void setSyncInput(final InputModel inputModel) {
    this.inputModel = inputModel;
    syncRefresh();
  }

  public void setInputModel(final InputModel inputModel) {
    this.inputModel = inputModel;
    refresh();
  }

  public void refresh() {
    getDisplay().asyncExec(new Runnable() {
      @Override
      public void run() {
        syncRefresh();
      }
    });
  }

  public void syncRefresh() {
    if (inputModel == null || isViewerDisposed()) {
      return;
    }
    IStructuredSelection selection = getSelection();
    getTableViewer().setInput(inputModel.getInput());
    setSelection(selection.toArray());
    resetEditorTable();
  }

  @Override
  public void addMouseListener(MouseListener listener) {
    getTable().addMouseListener(listener);
  }

  public void addSelectionChangedListener(ISelectionChangedListener listener) {
    getTableViewer().addSelectionChangedListener(listener);
  }

  public void addCheckStateListener(ICheckStateListener listener) {
    boolean isCheckTreeViewer = getTableViewer() instanceof CheckboxTreeViewer;
    if (isCheckTreeViewer) {
      ((CheckboxTreeViewer) getTableViewer()).addCheckStateListener(listener);
    }
  }

  public void setSelectionProvider(IWorkbenchPartSite site) {
    site.setSelectionProvider(getTableViewer());
  }

  public void registerContextMenu(IWorkbenchPartSite site) {
    if (site != null) {
      site.registerContextMenu(menuManager, getTableViewer());
      site.setSelectionProvider(getTableViewer());
    }
  }

  protected void hookContextMenu() {
    menuManager = new MenuManager("#TablePanePopupMenu");
    menuManager.setRemoveAllWhenShown(true);
    contextMenu = menuManager.createContextMenu(getTable());
    getTable().setMenu(contextMenu);
    final TablePaneActionGroup actionGroup = tableInfo.getActionGroup();
    if (actionGroup != null) {
      actionGroup.setTablePane(this);
      actionGroup.makeActions();
      actionGroup.setColumnViewer(getTableViewer());
      menuManager.addMenuListener(new IMenuListener() {
        public void menuAboutToShow(IMenuManager manager) {
          actionGroup.fillContextMenu(manager);
        }
      });
    }
  }

  public Menu getContextMenu() {
    return contextMenu;
  }

  public MenuManager getMenuManager() {
    return menuManager;
  }

  @Override
  public boolean setFocus() {
    if (getTable() != null) {
      getTable().setFocus();
    }
    return super.setFocus();
  }

  protected boolean isViewerDisposed() {
    return getTableViewer() == null || getTableViewer().getControl() == null
        || getTableViewer().getControl().isDisposed();
  }

  public abstract ColumnViewer getTableViewer();

  public abstract Control getTable();

  public abstract int[] getColumnOrder();

  public abstract void setColumnOrder(int[] columnOrder);

  public abstract Item[] getColumns();

  public TableInfo getTableInfo() {
    return tableInfo;
  }

  public void setTableInfo(TableInfo tableInfo) {
    this.tableInfo = tableInfo;
  }

  public boolean isConfigChanged() {
    return configChanged;
  }

  public void setConfigChanged(boolean configChanged) {
    this.configChanged = configChanged;
  }

  public Map<String, Item> getTreeColumnMap() {
    return treeColumnMap;
  }

  public void setTreeColumnMap(Map<String, Item> treeColumnMap) {
    this.treeColumnMap = treeColumnMap;
  }

  private void saveColumnSetting() {
    if (tableInfo.isCanSave() && tableInfo.isCanSettingColumn()) {
      resetColumns();
      SortUtil.sort(tableInfo.getColumnInfoList(), true, ColumnInfo.ORDER_FIELD);
      TableConfigFactory.saveTableConfig(tableInfo);
    }
  }

  public void resetColumns() {
    int[] columnOrder = getColumnOrder();
    Item[] columns = getColumns();
    for (int i = 0; i < columnOrder.length; i++) {
      int order = columnOrder[i];
      Item item = columns[order];
      boolean resizable = true;
      if (item instanceof TreeColumn) {
        TreeColumn column = (TreeColumn) item;
        resizable = column.getResizable();

        ColumnInfo columnInfo = (ColumnInfo) column.getData("columnInfo");
        if (resizable) {
          columnInfo.setWidth(column.getWidth());
        }
        columnInfo.setOrder(i);
        // columnInfo.setStyle(column.getStyle());
        // columnInfo.setAlignment(column.getAlignment());
        columnInfo.setVisible(resizable);
      } else {
        TableColumn column = (TableColumn) item;
        resizable = column.getResizable();

        ColumnInfo columnInfo = (ColumnInfo) column.getData("columnInfo");
        if (resizable) {
          columnInfo.setWidth(column.getWidth());
        }
        columnInfo.setOrder(i);
        // columnInfo.setStyle(column.getStyle());
        // columnInfo.setAlignment(column.getAlignment());
        columnInfo.setVisible(resizable);
      }
    }
  }

  private class TablePaneDisposeListener implements DisposeListener {

    @Override
    public void widgetDisposed(DisposeEvent event) {
      if (configChanged) {
        saveColumnSetting();
      }
      if (propertyChangedListener != null) {
        propertyChangedListener.dispose();
      }
      tablePaneRefreshSupport.dispose();
    }
  }

}